using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Boo.Lang.Compiler.Pipelines;
using Boo.Lang.Compiler;
using Boo.Lang.Compiler.IO;
using System.Reflection;

namespace GG_ScreenConsole
{
    public class ScreenConsole
    {
        public Rectangle Bounds = new Rectangle(0,280,800,200);
        Texture2D Image;
        SpriteFont Font;
        List<Assembly> Assemblies;
        BooCompiler Compiler = new BooCompiler(); //You can read up on boo here: http://boo.codehaus.org/
        
        string[] log;
        string Input = "";

        #region Keys
        public Keys ToggleConsole = Keys.Tab;
        public Keys Confirm = Keys.Enter;
        public KeyboardState OldState;
        #endregion

        bool showConsole = false;

        public ScreenConsole(
            SpriteFont _Font,
            List<Assembly> _Assemblies,
            int LogMax,
            Texture2D _Image,
            Keys _ToggleConsole,
            Keys _Confirm,
            bool ShouldInitialize = true)
        {
            log = new string[LogMax];
            Font = _Font;
            Assemblies = _Assemblies;
            Image = _Image;
            ToggleConsole = _ToggleConsole;
            Confirm = _Confirm;
            if (ShouldInitialize) Initialize();
        }

        public void Initialize()
        {
            Compiler.Parameters.Pipeline = new CompileToMemory();
            foreach (Assembly A in Assemblies)
            {
                Compiler.Parameters.References.Add(A);
            }
        }

        public void Draw( //Draw the console and log onscreen
            SpriteBatch spriteBatch)
        {
            if (showConsole)
            {
                spriteBatch.Draw(Image, Bounds, new Color(0, 0, 0, 180));
                int LitY = 0;
                for (int y = Bounds.Y + Bounds.Height - (int)Font.MeasureString("Q").Y * 2; y > Bounds.Y; y -= (int)Font.MeasureString("Q").Y)
                {
                    if (log[LitY] != null)
                    {
                        spriteBatch.DrawString(Font, log[LitY], new Vector2(Bounds.X, y), Color.White);
                    }
                    LitY++;
                }
                spriteBatch.DrawString(Font, Input, new Vector2(Bounds.X, Bounds.Y + Bounds.Height - (int)Font.MeasureString("Q").Y), Color.White);
            }
        }

        public void Update( //Update the Console, detect keys and whatnot
            KeyboardState State)
        {
            if (OldState==null)
            {
                OldState = State;
            }
            if (!OldState.IsKeyDown(ToggleConsole))
            {
                if (State.IsKeyDown(ToggleConsole))
                {
                    showConsole = !showConsole;
                }
            }
            if (showConsole)
            {
                if (!OldState.IsKeyDown(Confirm))
                {
                    if (State.IsKeyDown(Confirm))
                    {
                        RunCode(Input);
                        Input = "";
                    }
                }
                for (int si = 0; si < State.GetPressedKeys().Length; si++)
                {
                    if (State.GetPressedKeys()[si] != Confirm && State.GetPressedKeys()[si] != ToggleConsole)
                    {
                        if (State.IsKeyDown(State.GetPressedKeys()[si]) && !OldState.IsKeyDown(State.GetPressedKeys()[si]))
                        {
                            if (State.IsKeyDown(Keys.Back) && Input.Length > 0)
                            {
                                Input = Input.Substring(0, Input.Length - 1);
                            }
                            Input += ConvertKeyToChar(State.GetPressedKeys()[si], (State.IsKeyDown(Keys.LeftShift) || State.IsKeyDown(Keys.RightShift))).ToString();
                        }
                    }
                }
            }
            OldState = State;
        }

        #region Update Overloads
        public void Update()
        {
            Update(Keyboard.GetState());
        }
        #endregion

        public void RunCode( //Runs the imput string with the assemblies
            string Code)
        {
            Code = "static def Activate():\n " + Code;
            foreach (Assembly asm in Assemblies)
            {
                var topLevel = asm.GetTypes()
                    .Select(t => GetTopLevelNamespace(t))
                    .Distinct();
                foreach (var ns in topLevel)
                {
                    if (ns != null && ns!="")
                    {
                        Code = "import " + ns.ToString() + "\n" + Code;
                    }
                }
            }
            StringInput SI_Code = new StringInput("Code", Code);
            Compiler.Parameters.Input.Clear();
            Compiler.Parameters.Input.Add(SI_Code);
            CompilerContext BooContext = Compiler.Run();
            if (BooContext.GeneratedAssembly == null)
            {
                foreach (CompilerError error in BooContext.Errors)
                    Write(error.ToString());
            }
            else
            {
                Type scriptModule = BooContext.GeneratedAssembly.GetType("CodeModule");
                MethodInfo stringManip = scriptModule.GetMethod("Activate");
                string output = (string)stringManip.Invoke(this, null);
            }
        }

        public void Write( //Writes to console
            string s)
        {
            for (int x = log.Length - 2; x >= 0; x--)
            {
                log[x + 1] = log[x];
            }
            log[0] = s;
        }

        #region Private utility methods
        string ConvertKeyToChar(Keys key, bool shift)
        {
            switch (key)
            {
                case Keys.Space: return " ";

                // Escape Sequences 
                case Keys.Enter: return "\n";                         // Create a new line 
                case Keys.Tab: return "\t";                           // Tab to the right 

                // D-Numerics (strip above the alphabet) 
                case Keys.D0: return shift ? ")" : "0";
                case Keys.D1: return shift ? "!" : "1";
                case Keys.D2: return shift ? "@" : "2";
                case Keys.D3: return shift ? "#" : "3";
                case Keys.D4: return shift ? "$" : "4";
                case Keys.D5: return shift ? "%" : "5";
                case Keys.D6: return shift ? "^" : "6";
                case Keys.D7: return shift ? "&" : "7";
                case Keys.D8: return shift ? "*" : "8";
                case Keys.D9: return shift ? "(" : "9";

                // Numpad 
                case Keys.NumPad0: return "0";
                case Keys.NumPad1: return "1";
                case Keys.NumPad2: return "2";
                case Keys.NumPad3: return "3";
                case Keys.NumPad4: return "4";
                case Keys.NumPad5: return "5";
                case Keys.NumPad6: return "6";
                case Keys.NumPad7: return "7";
                case Keys.NumPad8: return "8";
                case Keys.NumPad9: return "9";
                case Keys.Add: return "+";
                case Keys.Subtract: return "-";
                case Keys.Multiply: return "*";
                case Keys.Divide: return "/";
                case Keys.Decimal: return ".";

                // Alphabet 
                case Keys.A: return shift ? "A" : "a";
                case Keys.B: return shift ? "B" : "b";
                case Keys.C: return shift ? "C" : "c";
                case Keys.D: return shift ? "D" : "d";
                case Keys.E: return shift ? "E" : "e";
                case Keys.F: return shift ? "F" : "f";
                case Keys.G: return shift ? "G" : "g";
                case Keys.H: return shift ? "H" : "h";
                case Keys.I: return shift ? "I" : "i";
                case Keys.J: return shift ? "J" : "j";
                case Keys.K: return shift ? "K" : "k";
                case Keys.L: return shift ? "L" : "l";
                case Keys.M: return shift ? "M" : "m";
                case Keys.N: return shift ? "N" : "n";
                case Keys.O: return shift ? "O" : "o";
                case Keys.P: return shift ? "P" : "p";
                case Keys.Q: return shift ? "Q" : "q";
                case Keys.R: return shift ? "R" : "r";
                case Keys.S: return shift ? "S" : "s";
                case Keys.T: return shift ? "T" : "t";
                case Keys.U: return shift ? "U" : "u";
                case Keys.V: return shift ? "V" : "v";
                case Keys.W: return shift ? "W" : "w";
                case Keys.X: return shift ? "X" : "x";
                case Keys.Y: return shift ? "Y" : "y";
                case Keys.Z: return shift ? "Z" : "z";

                // Oem 
                case Keys.OemOpenBrackets: return shift ? "{" : "[";
                case Keys.OemCloseBrackets: return shift ? "}" : "]";
                case Keys.OemComma: return shift ? "<" : ",";
                case Keys.OemPeriod: return shift ? ">" : ".";
                case Keys.OemMinus: return shift ? "_" : "-";
                case Keys.OemPlus: return shift ? "+" : "=";
                case Keys.OemQuestion: return shift ? "?" : "/";
                case Keys.OemSemicolon: return shift ? ":" : ";";
                case Keys.OemQuotes: return shift ? "\"" : "'";
                case Keys.OemPipe: return shift ? "|" : "\\";
                case Keys.OemTilde: return shift ? "~" : "`";
            }

            return string.Empty;
        } 
        string GetTopLevelNamespace(Type t)
        {
            string ns = t.Namespace ?? "";
            int firstDot = ns.IndexOf('.');
            return firstDot == -1 ? ns : ns.Substring(0, firstDot);
        }
        #endregion
    }
}
